﻿using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using log4net;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using AppFx.BizTalk.IO.Streams.Spec;

namespace AppFx.BizTalk.Dynamics.CRM.Helpers.CRMDataExtract
{   
    public class CRMDownloadManager : IDisposable, IDownloadManager
    {
        private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private readonly OrganizationService _crmService;
        private readonly string _fetchXmlRequest;
        private string _pagingCookie = null;
        private int _pageNumber = 1;
        private bool _firstRead = true;
        private bool _lastRead;
        private QueryExpression _queryExpression;

        public bool MorePages { get; set; }
        public string MessageStart { get; set; }
        public string MessageEnd { get; set; }        
        public int FetchCount { get; set; }
        //private int RecordCount { get; set; }

        public CRMDownloadManager(string crmConnectionString, string fetchXmlRequest)
        {
            var crmConnection = CrmConnection.Parse(crmConnectionString);
            _crmService = new OrganizationService(crmConnection);
            _fetchXmlRequest = fetchXmlRequest;
            MorePages = true;
            FetchCount = 2000;
        }

        public CRMDownloadManager(OrganizationService crmService, string fetchXmlRequest)
        {
            _crmService = crmService;
            _fetchXmlRequest = fetchXmlRequest;
            MorePages = true;
            FetchCount = 2000;
        }

        public byte[] GetNext()
        {
            byte[] nextBytes = null;
            if (_firstRead)
            {
                // Build fetchXml string with the placeholders.
                var xml = CreateXml(_fetchXmlRequest, _pagingCookie, _pageNumber, FetchCount);                
                _queryExpression = _crmService.FetchXmlToQueryExpression(xml);

                var messageStart = GetMesageStart();
                var firstPage = GetNextPage();
                nextBytes = new byte[messageStart.Length + firstPage.Length];
                messageStart.CopyTo(nextBytes, 0);
                firstPage.CopyTo(nextBytes, messageStart.Length);
                _firstRead = false;
                return nextBytes;
            }
            else
            {
                if (_lastRead)
                {
                    Log.Debug("CRM Extract Download Complete");                    
                    nextBytes = new byte[0];
                    return nextBytes;
                }
                if (MorePages)
                {
                    nextBytes = GetNextPage();
                    if (MorePages) return nextBytes;

                    var endMessageBytes = GetMesageEnd();
                    var mergedNextBytes = new byte[nextBytes.Length + endMessageBytes.Length];
                    Buffer.BlockCopy(nextBytes, 0, mergedNextBytes, 0, nextBytes.Length);
                    Buffer.BlockCopy(endMessageBytes, 0, mergedNextBytes, nextBytes.Length, endMessageBytes.Length);
                        
                    _lastRead = true;
                    return mergedNextBytes;
                }
                else
                {
                    nextBytes = GetMesageEnd();
                    _lastRead = true;
                }
            }
            return nextBytes;
        }

        public byte[] GetNextPage()
        {            
            var crmResponse = _crmService.RetrieveMultiple(_queryExpression);            
            var returnCollection = crmResponse.Entities;            

            Log.Debug(string.Format("Retrieving records from CRM - page {0}", _pageNumber));
            var dataStream = new MemoryStream();
            foreach (var crmEntity in returnCollection)
            {
                var formatter = new FlattenedXmlFormatter {PrefixRootEntityAttributes = true};
                formatter.Format(dataStream, crmEntity);
            }

            // Check for morerecords, if it returns 1.
            if (crmResponse.MoreRecords)
            {
                Log.DebugFormat("Page: {0}, Paging Cookie: {1}", _pageNumber, _pagingCookie);
                //queryServicios.PageInfo.PagingCookie = (pageNumber == 1) ? null : multiResponse.EntityCollection.PagingCookie;

                // Increment the page number to retrieve the next page.
                _pageNumber++;
                _queryExpression.PageInfo.PageNumber = _pageNumber;
                _queryExpression.PageInfo.PagingCookie = crmResponse.PagingCookie;
                _pagingCookie = crmResponse.PagingCookie;
            }
            else
                MorePages = false;

#if DEBUG
            dataStream.Flush();
            dataStream.Seek(0, SeekOrigin.Begin);
            var reader = new StreamReader(dataStream);            
            //Log.Debug(reader.ReadToEnd());
            dataStream.Seek(0, SeekOrigin.Begin);
#endif
            dataStream.Flush();
            dataStream.Seek(0, SeekOrigin.Begin);
            return dataStream.ToArray();
        }

        public byte[] GetMesageStart()
        {
            using (var ms = new MemoryStream())
            {
                var writer = new StreamWriter(ms);
                writer.Write(MessageStart);
                writer.Flush();
                ms.Flush();
                return ms.ToArray();
            }
        }

        public byte[] GetMesageEnd()
        {
            using (var ms = new MemoryStream())
            {
                var writer = new StreamWriter(ms);
                writer.Write(MessageEnd);
                writer.Flush();
                ms.Flush();
                return ms.ToArray();
            }
        }

        public void Dispose()
        {
            if (_crmService != null)
            {
                _crmService.Dispose();
            }
        }

        public string CreateXml(string xml, string cookie, int page, int count)
        {
            var stringReader = new StringReader(xml);
            var reader = new XmlTextReader(stringReader);

            // Load document
            var doc = new XmlDocument();
            doc.Load(reader);

            return CreateXml(doc, cookie, page, count);
        }

        public string CreateXml(XmlDocument doc, string cookie, int page, int count)
        {
            Debug.Assert(doc.DocumentElement != null, "doc.DocumentElement != null");
            var attrs = doc.DocumentElement.Attributes;

            if (cookie != null)
            {
                var pagingAttr = doc.CreateAttribute("paging-cookie");
                pagingAttr.Value = cookie;
                attrs.Append(pagingAttr);
            }

            var pageAttr = doc.CreateAttribute("page");
            pageAttr.Value = Convert.ToString(page);
            attrs.Append(pageAttr);

            var countAttr = doc.CreateAttribute("count");
            countAttr.Value = Convert.ToString(count);
            attrs.Append(countAttr);

            var sb = new StringBuilder(1024);
            var stringWriter = new StringWriter(sb);

            var writer = new XmlTextWriter(stringWriter);
            doc.WriteTo(writer);
            writer.Close();

            return sb.ToString();
        }
    }
}

